package com.niiwoo.civet.trade.service.local.kaola;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Preconditions;
import com.niiwoo.civet.account.dto.common.BankCardDTO;
import com.niiwoo.civet.account.enums.CanRefundEnum;
import com.niiwoo.civet.account.service.bankcard.BankCardDubboService;
import com.niiwoo.civet.base.enums.FeeTypeEnum;
import com.niiwoo.civet.trade.constant.DatePatternConstant;
import com.niiwoo.civet.trade.constant.RedissonLockConstant;
import com.niiwoo.civet.trade.dao.entity.*;
import com.niiwoo.civet.trade.dao.mapper.*;
import com.niiwoo.civet.trade.dto.PrepaymentAmountDTO;
import com.niiwoo.civet.trade.dto.ProjectBorrowerPlanNewDTO;
import com.niiwoo.civet.trade.dto.Tuple2;
import com.niiwoo.civet.trade.dto.kaola.data.RepayPlan;
import com.niiwoo.civet.trade.dto.kaola.request.OverdueRepaymentRequest;
import com.niiwoo.civet.trade.dto.kaola.request.PreRepaymentRequest;
import com.niiwoo.civet.trade.dto.kaola.request.RepaymentRequest;
import com.niiwoo.civet.trade.dto.kaola.response.BaseResponse;
import com.niiwoo.civet.trade.dto.kaola.response.GetOrderStatResponse;
import com.niiwoo.civet.trade.dto.kaola.response.PreRepaymentResponse;
import com.niiwoo.civet.trade.dto.kaola.response.RepaymentResponse;
import com.niiwoo.civet.trade.dto.request.kaola.KaoLaLiCaiRepayRequestDTO;
import com.niiwoo.civet.trade.dto.response.kaola.KaoLaLiCaiRepayResponseDTO;
import com.niiwoo.civet.trade.enums.BorrowerPlanRefundStatusEnum;
import com.niiwoo.civet.trade.enums.FadadaContractEnum;
import com.niiwoo.civet.trade.enums.FadadaContractTemplateEnum;
import com.niiwoo.civet.trade.enums.RepayStatusEnum;
import com.niiwoo.civet.trade.enums.kaola.BadLoansRepayMethodEmun;
import com.niiwoo.civet.trade.enums.kaola.KaoLaLiCaiRepayTypeEnum;
import com.niiwoo.civet.trade.service.local.fund.FundRepaymentService;
import com.niiwoo.civet.trade.service.local.refund.CommonLocalService;
import com.niiwoo.civet.trade.utils.RepaymentUtil;
import com.niiwoo.tripod.provider.exception.BizException;
import com.niiwoo.tripod.service.component.SnowflakeIdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.joda.time.DateTime;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;

import static com.niiwoo.civet.trade.dao.entity.KaoLaRealRepayment.RepayMethodEnum.*;

/**
 * 考拉还款服务.
 *
 * @author lichuanzhi
 * @date 2017-12-28 10:42
 */

@Slf4j
@Service
public class KaoLaLiCaiRepaymentService {

    @Autowired
    private ProjectBorrowerSummaryMapperExt projectBorrowerSummaryMapper;
    @Autowired
    private KaoLaRealRepaymentMapperExt kaoLaRealRepaymentMapper;
    @Autowired
    private KaoLaProjectRepaymentMapperExt kaoLaProjectRepaymentMapper;
    @Autowired
    private KaoLaOrderInfoMapperExt kaoLaOrderInfoMapper;
    @Autowired
    private ProjectBorrowerPlanMapperExt projectBorrowerPlanMapper;
    @Autowired
    private ProjectBorrowerPlanNewMapperExt projectBorrowerPlanNewMapper;
    @Autowired
    private ProjectBorrowerPlanDetailMapperExt borrowerPlanDetailMapperExt;
    @Autowired
    private ProjectContractMapperExt projectContractMapperExt;
    @Autowired
    private ProjectCreditorFundMapperExt projectCreditorFundMapper;

    @Autowired
    private KaoLaLiCaiRequestServiceImpl kaoLaLiCaiRequestService;
    @Autowired
    private FundRepaymentService fundRepaymentService;
    @Autowired
    private CommonLocalService commonLocalService;

    @Reference(version = "1.0.0")
    private BankCardDubboService bankCardDubboService;

    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;
    @Autowired
    private RedissonClient redissonClient;

    public KaoLaLiCaiRepayResponseDTO repayment(KaoLaLiCaiRepayRequestDTO dto) {
        log.info("考拉**正常还款**处理开始，KaoLaLiCaiRepayRequestDTO={}", JSON.toJSONString(dto));
        Long projectId = dto.getProjectId();
        ProjectBorrowerSummary summary = getProjectBorrowerSummary(projectId);

        //koala的所有还款操作都是互斥的有且只能有一个正常触发
        RLock lock = redissonClient.getLock(RedissonLockConstant.KAOLA_REPAY.concat(dto.getUserId()).concat(RedissonLockConstant
                .KEY_NS_DELIMIT).concat(String.valueOf(projectId)));
        KaoLaRealRepayment kaoLaRealRepayment = null;
        try {

            if (!lock.tryLock()) {
                throw new BizException("TRD21001");
            }
            checkStatus(projectId, summary, NORMAL_REPAY);

            //如果是回购，则现将代偿那一期还掉，后面所有的还款都还回购那一期
            KaoLaRealRepayment buyBackRepayment = getBuyBackRepayment(summary.getProjectId());
            if (Objects.nonNull(buyBackRepayment)) {
                Tuple2<BankCardDTO, String> bankCardAndOrderId = getOrderAndIdCard(dto.getUserId(), dto.getProjectId());
                kaoLaRealRepayment = buyBackRepayment(summary, bankCardAndOrderId, buyBackRepayment);
            } else {
                //初始写入考拉实际还款记录
                kaoLaRealRepayment = initRepayment(dto, summary);
            }
            log.info("考拉理财正常还款,项目ID={},插入的kaoLaRealrePaymentId={}", projectId,kaoLaRealRepayment.getId());

            //请求考拉正常还款请求,异步获取还款结果
            RepaymentRequest repaymentRequest = new RepaymentRequest();
            RepaymentResponse repayment;
            try {
                BeanUtils.copyProperties(kaoLaRealRepayment,repaymentRequest);
                repayment = kaoLaLiCaiRequestService.repayment(repaymentRequest);
            } catch (Throwable e) {
                log.error("考拉理财**正常还款**异常,项目ID={}",e,projectId);
                this.updateKaoLaRepayRequestResult(kaoLaRealRepayment.getId(),"1",e.getMessage());
                throw e;
            }

            handleResponse(kaoLaRealRepayment, repayment);
        } finally {
            lock.unlock();
        }

        return getResponseForApp(summary, kaoLaRealRepayment.getRepayAmount(), kaoLaRealRepayment.getSerialNo());
    }


    public KaoLaLiCaiRepayResponseDTO preRepayment(KaoLaLiCaiRepayRequestDTO dto) {
        log.info("考拉**提前结清**处理开始，KaoLaLiCaiRepayRequestDTO={}", JSON.toJSONString(dto));
        Long projectId = dto.getProjectId();
        ProjectBorrowerSummary summary = getProjectBorrowerSummary(projectId);

        RLock lock = redissonClient.getLock(RedissonLockConstant.KAOLA_REPAY.concat(dto.getUserId()).concat(RedissonLockConstant
                .KEY_NS_DELIMIT).concat(String.valueOf(projectId)));
        KaoLaRealRepayment kaoLaRealRepayment = null;
        try {
            if (!lock.tryLock()) {
                throw new BizException("TRD21001");
            }
            checkStatus(projectId, summary, PRE_REPAY);

            //初始写入考拉实际还款记录
            kaoLaRealRepayment = initPreRepayment(dto,summary);
            log.info("考拉理财提前结清,项目ID={},插入的kaoLaRealrePaymentId={}", projectId,kaoLaRealRepayment.getId());

            //请求考拉正常还款请求,异步获取还款结果
            PreRepaymentRequest preRepaymentRequest = new PreRepaymentRequest();
            PreRepaymentResponse repaymentResponse;
            try {
                BeanUtils.copyProperties(kaoLaRealRepayment,preRepaymentRequest);
                repaymentResponse = kaoLaLiCaiRequestService.preRepayment(preRepaymentRequest);
            } catch (Throwable e) {
                log.error("考拉理财**提前结清**还款异常,项目ID={}",e,projectId);
                this.updateKaoLaRepayRequestResult(kaoLaRealRepayment.getId(),"1",e.getMessage());
                throw e;
            }

            handleResponse(kaoLaRealRepayment, repaymentResponse);
        } finally {
            lock.unlock();
        }

        return getResponseForApp(summary, kaoLaRealRepayment.getRepayAmount(), kaoLaRealRepayment.getSerialNo());
    }

    public KaoLaLiCaiRepayResponseDTO overdueRepayment(KaoLaLiCaiRepayRequestDTO dto) {
        log.info("考拉**逾期还款**处理开始，KaoLaLiCaiRepayRequestDTO={}", JSON.toJSONString(dto));
        Long projectId = dto.getProjectId();
        ProjectBorrowerSummary summary = getProjectBorrowerSummary(projectId);
        Tuple2<BankCardDTO, String> bankCardAndOrderId = getOrderAndIdCard(dto.getUserId(), dto.getProjectId());

        RLock lock = redissonClient.getLock(RedissonLockConstant.KAOLA_REPAY.concat(dto.getUserId()).concat(RedissonLockConstant
                .KEY_NS_DELIMIT).concat(String.valueOf(projectId)));
        KaoLaRealRepayment kaoLaRealRepayment = null;
        boolean isBadDebt = RepayStatusEnum.BADLOANS == RepayStatusEnum.enumOf(summary.getRepayStatus());

        try {
            if (!lock.tryLock()) {
                throw new BizException("TRD21001");
            }
            checkStatus(projectId,summary,OVERDUE_REPAY);

            //如果非坏账却是回购，则现将代偿那一期还掉，后面所有的还款都还回购那一期.考拉代偿的那一期不等于summary的当前期则判断为非正常回购
            KaoLaRealRepayment buyBackRepayment = getBuyBackRepayment(summary.getProjectId());
            if (!isBadDebt && Objects.nonNull(buyBackRepayment)) {
                kaoLaRealRepayment = buyBackRepayment(summary, bankCardAndOrderId, buyBackRepayment);
            }else if (isBadDebt && isBadDebtRefundOverdue(summary)) {
                //坏账，判断逾期那一期是否已经成功还款，如果成功还款则直接结清
                lock.unlock();
                return badLoansSettleRepayment(summary);
            } else {
                if (isOverduePartRepay(summary)) {
                    kaoLaRealRepayment = initOverduePartRepayment(summary, bankCardAndOrderId, isBadDebt);
                } else {
                    kaoLaRealRepayment = initOverdueRepayment(summary, bankCardAndOrderId, isBadDebt, buyBackRepayment);
                }
            }

            log.info("考拉理财逾期还款,项目ID={},插入的kaoLaRealrePaymentId={}", projectId,kaoLaRealRepayment.getId());

            //请求考拉正常还款请求,异步获取还款结果
            OverdueRepaymentRequest overdueRepaymentRequest = new OverdueRepaymentRequest();
            RepaymentResponse repaymentResponse;
            try {
                BeanUtils.copyProperties(kaoLaRealRepayment,overdueRepaymentRequest);
                repaymentResponse = kaoLaLiCaiRequestService.overdueRepayment(overdueRepaymentRequest);
            } catch (Throwable e) {
                log.error("考拉理财**逾期还款**异常,项目ID={}",e,projectId);
                this.updateKaoLaRepayRequestResult(kaoLaRealRepayment.getId(),"1",e.getMessage());
                throw e;
            }

            handleResponse(kaoLaRealRepayment, repaymentResponse);
        } finally {
            lock.unlock();
        }

        return getResponseForApp(summary, kaoLaRealRepayment.getRepayAmount().add(kaoLaRealRepayment.getOverdueInterest()), kaoLaRealRepayment.getSerialNo());
    }

    /**
     * 坏账还款，拆分后由 1.考拉坏账逾期还款成功回调 2.前端在逾期已成功还款但结清失败，这两种场景下触发结清.
     * <p>
     * 标的还款状态和当前期数都不变，但还款计划中逾期的那一期的状态如果是成功第一次还款则必须更新为已逾期还款；
     * 主要需要注意的是在有些地方拆分后需要将当前期+1 即从坏账那一期开始计算
     *
     * @param summary 标的还款概要
     * @return 还款响应
     */

    public KaoLaLiCaiRepayResponseDTO badLoansSettleRepayment(ProjectBorrowerSummary summary) {
        log.info("考拉**坏账结清**处理开始，projectId={},repayNo={}", summary.getProjectId(), summary.getCurrentPeriod());
        Long projectId = summary.getProjectId();
        Tuple2<BankCardDTO, String> bankCardAndOrderId = getOrderAndIdCard(summary.getBorrowerUserId(), summary.getProjectId());

        RLock lock = redissonClient.getLock(RedissonLockConstant.KAOLA_REPAY.concat(summary.getBorrowerUserId()).concat(RedissonLockConstant
                .KEY_NS_DELIMIT).concat(String.valueOf(projectId)));
        KaoLaRealRepayment kaoLaRealRepayment = null;
        try {
            if (!lock.tryLock()) {
                throw new BizException("TRD21001");
            }

            if (isBadloansPartRepay(summary)) {
                kaoLaRealRepayment = initBadDebtPartRepayment(summary, bankCardAndOrderId);
            } else {
                kaoLaRealRepayment = initBadDebtRepayment(summary, bankCardAndOrderId);
            }
            log.info("考拉理财##坏账结清,项目ID={},插入的kaoLaRealrePaymentId={}", projectId,kaoLaRealRepayment.getId());

            //请求考拉正常还款请求,异步获取还款结果
            OverdueRepaymentRequest overdueRepaymentRequest = new OverdueRepaymentRequest();
            RepaymentResponse repaymentResponse;
            try {
                BeanUtils.copyProperties(kaoLaRealRepayment,overdueRepaymentRequest);
                repaymentResponse = kaoLaLiCaiRequestService.overdueRepayment(overdueRepaymentRequest);
            } catch (Throwable e) {
                log.error("考拉理财##坏账结清##异常,项目ID={}",e,projectId);
                this.updateKaoLaRepayRequestResult(kaoLaRealRepayment.getId(),"1",e.getMessage());
                throw e;
            }

            handleResponse(kaoLaRealRepayment, repaymentResponse);
        } finally {
            lock.unlock();
        }

        return getResponseForApp(summary, kaoLaRealRepayment.getRepayAmount().add(kaoLaRealRepayment.getOverdueInterest()), kaoLaRealRepayment.getSerialNo());
    }

    /**
     * 我们app上展示的所有费用都以你我计划为准，因为app用户看到还款计划都是你我的，
     * 单发送给考拉的请求（本金，利息等只要是涉及到考拉那边的投资人利益的资金项都得以考拉的为准）则都需以考拉的计划为准.
     *
     * @param summary 项目还款概要
     * @return 立即还款总额
     */
    public BigDecimal getImmediateRepayAmount(ProjectBorrowerSummary summary) {
        ProjectBorrowerPlanNewDTO projectBorrowerPlan = Optional.ofNullable(projectBorrowerPlanNewMapper.selectByProjectIdAndPeroidForAmount(summary.getProjectId(),summary.getCurrentPeriod())).orElseThrow(() -> new BizException("TRD20005"));
        BigDecimal totalAmount = projectBorrowerPlan.getGuaranteeFee().add(projectBorrowerPlan.getManagementFee())
                .add(projectBorrowerPlan.getCapital()).add(projectBorrowerPlan.getInterest());
        RepayStatusEnum repayStatusEnum = RepayStatusEnum.enumOf(summary.getRepayStatus());

        // 可能存在部分还款，减去已还部分+剩余罚息
        if (RepayStatusEnum.OVERDUE == repayStatusEnum) {
            totalAmount = totalAmount
                    .subtract(projectBorrowerPlan.getRefundedCapital().add(projectBorrowerPlan.getRefundedInterest())
                            .add(projectBorrowerPlan.getRefundedManagementFee()).add(projectBorrowerPlan.getRefundedGuaranteeFee()))
                    .add(this.getPenaltyFee(summary));
        }
        if (RepayStatusEnum.BADLOANS == repayStatusEnum) {
            PrepaymentAmountDTO prepaymentAmount = this.calcNiiWooPreRePay(summary,true);
            totalAmount = prepaymentAmount.getPlatformManagerFee().add(prepaymentAmount.getCreditGuaranteeFee()).add(prepaymentAmount.getPrincipal()).add(
                    prepaymentAmount.getInterest()).add(this.getPenaltyFee(summary));
        }
        return totalAmount;
    }

    public PrepaymentAmountDTO calcNiiWooPreRePay(ProjectBorrowerSummary summary, boolean isBadDebt) {
        PrepaymentAmountDTO prepaymentAmount = new PrepaymentAmountDTO();
        Integer currentPeriod = summary.getCurrentPeriod();

        //获取标的所有未还款的计划
        List<ProjectBorrowerPlanNewDTO> projectBorrowerPlanList = projectBorrowerPlanNewMapper.selectByProjectIdForAmount(summary.getProjectId());
        for (ProjectBorrowerPlanNewDTO projectBorrowerPlan : projectBorrowerPlanList) {

            //坏账需要逾期那一期和坏账那一期的利息（对于已还逾期那一期的场景，此处并未查询出来所有只会满足projectBorrowerPlan.getCurrentPeriod().equals(currentPeriod + 1)），所以不会再算逾期那一期的利息
            // 提前结清则取当前期的利息
            if (isBadDebt){
                if(projectBorrowerPlan.getPeriod().equals(currentPeriod) || projectBorrowerPlan.getPeriod().equals(currentPeriod + 1)) {
                    prepaymentAmount.addInterest(projectBorrowerPlan.getInterest().subtract(projectBorrowerPlan.getRefundedInterest()));
                }
            } else {
                if (projectBorrowerPlan.getPeriod().equals(Integer.valueOf("1"))
                        || projectBorrowerPlan.getRefundDate().before(new DateTime(commonLocalService.getKaolaCurrentDate()).plusMonths(1).minusDays(1).toDate())) {
                    prepaymentAmount.addInterest(projectBorrowerPlan.getInterest().subtract(projectBorrowerPlan.getRefundedInterest()));
                }else{

                    //节省利息金额
                    prepaymentAmount.addCutCosts(projectBorrowerPlan.getInterest().subtract(projectBorrowerPlan.getRefundedInterest()));
                }
            }

            prepaymentAmount.addPrincipal(projectBorrowerPlan.getCapital().subtract(projectBorrowerPlan.getRefundedCapital()));
            prepaymentAmount.addPlatformManagerFee(projectBorrowerPlan.getManagementFee().subtract(projectBorrowerPlan.getRefundedManagementFee()));
            prepaymentAmount.addCreditGuaranteeFee(projectBorrowerPlan.getGuaranteeFee().subtract(projectBorrowerPlan.getRefundedGuaranteeFee()));
        }
        return prepaymentAmount;
    }

    public boolean repayDayRefundAndReRePay(ProjectBorrowerSummary summary) {
        if (summary.getCurrentPeriod() <= 1) {
            return false;
        }
        ProjectBorrowerPlanNew projectBorrowerPlan = projectBorrowerPlanNewMapper.selectByProjectAndPeriod(summary
                .getProjectId(), summary.getCurrentPeriod() - 1);
        return projectBorrowerPlan != null && new DateTime(projectBorrowerPlan.getRefundDate()).toString("yyyyMMdd").equals(new DateTime().toString("yyyyMMdd"));
    }

    public CanRefundEnum isProcessing(Long projectId) {
        return kaoLaRealRepaymentMapper.countProcessingRepay(projectId) > 0 ? CanRefundEnum.REFUNDING : CanRefundEnum.CAN_REFUND;
    }

    public KaoLaRealRepayment getBuyBackRepayment(Long projectId) {
        KaoLaRealRepayment kaoLaRealRepayment = kaoLaRealRepaymentMapper.selectPassiveKaoLaBuyBack(projectId);
        if (Objects.nonNull(kaoLaRealRepayment)) {
            return kaoLaRealRepayment;
        } else{
            KaoLaRealRepayment lastRepayment = kaoLaRealRepaymentMapper.selectRepaymentByRepayMethod(projectId, null);
            if(lastRepayment != null && lastRepayment.getRepayResultTips() != null && lastRepayment.getRepayResultTips().contains("计划")){
                KaoLaOrderInfo kaoLaOrderInfo = kaoLaOrderInfoMapper.selectByProjectId(projectId);
                GetOrderStatResponse orderStat = kaoLaLiCaiRequestService.getOrderStat(projectId, kaoLaOrderInfo.getLoanOrderId());
                if (orderStat != null && !CollectionUtils.isEmpty(orderStat.getRepay_plan())) {
                    for (RepayPlan repayPlan : orderStat.getRepay_plan()) {
                        if (repayPlan.getRepay_type().equals(KaoLaLiCaiRepayTypeEnum.REPAY_BUYBACK.getCode()) && repayPlan.is_repay()) {
                            return getKaoLaRepayment(repayPlan);
                        }
                    }
                }
            }
        }
        return null;
    }

    public KaoLaRealRepayment getAdvanceRepayment(ProjectBorrowerSummary summary, Integer advanceRepayNo) {
        KaoLaRealRepayment kaoLaRealRepayment = kaoLaRealRepaymentMapper.selectSuccessAdvanceByProjectIdAndPeriod(summary.getProjectId(), advanceRepayNo);
        if (Objects.nonNull(kaoLaRealRepayment)) {
            return kaoLaRealRepayment;
        } else{
            KaoLaOrderInfo kaoLaOrderInfo = kaoLaOrderInfoMapper.selectByProjectId(summary.getProjectId());
            GetOrderStatResponse orderStat = kaoLaLiCaiRequestService.getOrderStat(summary.getProjectId(), kaoLaOrderInfo.getLoanOrderId());
            if (orderStat != null && !CollectionUtils.isEmpty(orderStat.getRepay_plan())) {
                for (RepayPlan repayPlan : orderStat.getRepay_plan()) {
                    if (repayPlan.getRepay_type().equals(KaoLaLiCaiRepayTypeEnum.REPAY_ADVANCE.getCode())
                            && repayPlan.is_repay()
                            && repayPlan.getNo().equals(advanceRepayNo)) {
                        return getKaoLaRepayment(repayPlan);
                    }
                }
            }
        }
        return null;
    }


    private KaoLaRealRepayment getKaoLaRepayment(RepayPlan repayPlan) {
        KaoLaRealRepayment kaoLaRepayment = new KaoLaRealRepayment();
        kaoLaRepayment.setRepayNo(repayPlan.getNo());
        kaoLaRepayment.setPrincipal(repayPlan.getRepay_principal());
        kaoLaRepayment.setInterest(repayPlan.getRepay_interest());
        kaoLaRepayment.setInterestSplit(repayPlan.getRepay_interest_split());
        return kaoLaRepayment;
    }


    /**
     * 获取标的还款概要信息
     *
     * @param projectId 项目id
     * @return 标的还款概要
     */
    public ProjectBorrowerSummary getProjectBorrowerSummary(Long projectId) {

        //获取标的还款概要信息
        return Optional.ofNullable(projectBorrowerSummaryMapper.selectByPrimaryKey(projectId)).orElseThrow(() -> new
                BizException("TRD20001"));
    }

    private Tuple2<BankCardDTO,String> getOrderAndIdCard(String userId, Long projectId) {

        //获取用户身份证号
        BankCardDTO bankCardDTO = Optional.ofNullable(bankCardDubboService.getUserBankCardInfo(userId)).orElseThrow(
                () -> new IllegalStateException("用户或用户身份信息不存在"));
        bankCardDTO.setIdCard(bankCardDTO.getIdCard().toUpperCase());

        //获取考拉订单id
        String loanOrderId = Optional.ofNullable(kaoLaOrderInfoMapper.selectByProjectId(projectId)).map(KaoLaOrderInfo::getLoanOrderId).orElseThrow(
                () -> new BizException("TRD10001"));
        return new Tuple2<>(bankCardDTO, loanOrderId);
    }


    /**
     * 判断是否是逾期部分还款.
     *
     * @param summary 还款标的概要
     * @return 是否部分还款
     */
    private boolean isOverduePartRepay(ProjectBorrowerSummary summary) {
        ProjectBorrowerPlan projectBorrowerPlan = projectBorrowerPlanMapper.selectByProjectIdAndPeriods(summary.getProjectId(), summary.getCurrentPeriod());

        //去除已逾期还款的，在坏账第一次成功，第二次失败的时候会出现
        if (BorrowerPlanRefundStatusEnum.enumOf(projectBorrowerPlan.getRefundStatus()) != BorrowerPlanRefundStatusEnum.OVERDUE_REPAYMENT) {
            BigDecimal refundedTotal = projectBorrowerPlan.getRefundedGuaranteeFee().add(projectBorrowerPlan.getRefundedManagementFee())
                    .add(projectBorrowerPlan.getRefundedCapital()).add(projectBorrowerPlan.getRefundedInterest());
            return refundedTotal.compareTo(BigDecimal.ZERO) > 0;
        }
        return false;
    }

    /**
     * 判断是否是坏账结清部分还款.
     * <p>
     * 只要存在未还款期还了一部分即为部分还款
     *
     * @param summary 还款标的概要
     * @return 是否部分还款
     */
    private boolean isBadloansPartRepay(ProjectBorrowerSummary summary) {
        List<ProjectBorrowerPlan> plans = projectBorrowerPlanMapper.selectBorrowerPlanByProjectIdNotRefund(summary.getBorrowerUserId(), summary.getProjectId());
        return plans.stream().anyMatch(
                    plan -> plan.getRefundedGuaranteeFee().add(plan.getRefundedManagementFee()).add(plan.getRefundedCapital()).add(plan.getRefundedInterest())
                        .compareTo(BigDecimal.ZERO) > 0);
    }

    /**
     * 检查坏账时逾期那一期是否已经还款.
     *
     * @param summary 标的还款概要
     * @return 是否已坏账逾期还款
     */
    private boolean isBadDebtRefundOverdue(ProjectBorrowerSummary summary) {

        //也可以直接取你我还款计划中的当前期状态
        return Optional.ofNullable(projectBorrowerPlanMapper.selectBorrowerPlanByProjectIdAndCurrentPeriod(
                summary.getProjectId(), summary.getCurrentPeriod())).map(
                r -> BorrowerPlanRefundStatusEnum.enumOf(r.getRefundStatus()) == BorrowerPlanRefundStatusEnum.OVERDUE_REPAYMENT).orElse(false);
    }

    /**
     * 检查坏账时逾期那一期是否已经还款.
     *
     * @param summary 标的还款概要
     * @return 是否已坏账逾期还款
     */
    public boolean isBadDebtOverdueRefund(ProjectBorrowerSummary summary) {
        boolean isBadDebt = RepayStatusEnum.BADLOANS == RepayStatusEnum.enumOf(summary.getRepayStatus());
        return isBadDebt && this.isBadDebtRefundOverdue(summary);
    }

    private void checkStatus(Long projectId, ProjectBorrowerSummary summary, KaoLaRealRepayment.RepayMethodEnum repayMethodEnum) {

        /*
        以下场景借款详情页面不显示“提前结清”按钮及文案：
        （1）只有一期的极速借借款标
        （2）还款严重逾期；
        （3）考拉理财还款日当天已经操作了立即还款，当天无法再操作提前结清；
        （4）考拉理财普通逾期和严重逾期状态时不能提前结清；

        这里的校验，前端已经做了，可不做
         */
        Byte repayStatus = summary.getRepayStatus();
        switch (repayMethodEnum) {
            case NORMAL_REPAY:
                Preconditions.checkArgument(repayStatus.equals(RepayStatusEnum.NORMAL.getCode()), "**正常还款**标的还款状态错误，RepayStatus:{%s}", repayStatus);
                break;
            case OVERDUE_REPAY:
                Preconditions.checkArgument(repayStatus.equals(RepayStatusEnum.OVERDUE.getCode())
                        || repayStatus.equals(RepayStatusEnum.BADLOANS.getCode()), "**逾期还款**标的还款状态错误，RepayStatus:{%s}", repayStatus);
                break;
            case PRE_REPAY:
                Preconditions.checkArgument(repayStatus.equals(RepayStatusEnum.NORMAL.getCode()), "**提前还款**标的还款状态错误，RepayStatus:{%s}", repayStatus);

                //还款日当天，借款人立即还款成功后，限制借款人当天再操作全额还款。借款人点击全额还款时，弹窗文案：您已还本期，请次日再操作全额还款，谢谢.
                KaoLaRealRepayment kaoLaRealRepayment = kaoLaRealRepaymentMapper.selectRepaymentByRepayMethod(projectId, NORMAL_REPAY.getType());
                if (kaoLaRealRepayment != null
                        && KaoLaRealRepayment.RepayStatusEnum.SUCCESS == KaoLaRealRepayment.RepayStatusEnum.enumOf(kaoLaRealRepayment.getRepayCallbackStatus().intValue())
                        && FastDateFormat.getInstance(DatePatternConstant.MM_DD_YYYY).format(new Date()).equals(kaoLaRealRepayment.getRepayDate())) {

                    //文案协商由前端提供
                 throw new BizException("TRD21002","");
                }
                break;
            default:
        }


        //异常,原因是数据库中有一笔正常还款请求正在处理中
        if(CanRefundEnum.REFUNDING == isProcessing(projectId)){
            throw new BizException("TRD21001");
        }
    }

    private KaoLaRealRepayment initRepayment(KaoLaLiCaiRepayRequestDTO dto, ProjectBorrowerSummary summary) {
        //获取当前期的考拉还款计划
        KaoLaProjectRepayment kaoLaProjectRepayment = Optional.ofNullable(kaoLaProjectRepaymentMapper.selectByProjectIdAndPeriod(dto.getProjectId()
                , summary.getCurrentPeriod())).orElseThrow(() -> new BizException("TRD20005"));
        Tuple2<BankCardDTO, String> bankCardAndOrderId = getOrderAndIdCard(dto.getUserId(), dto.getProjectId());

        KaoLaRealRepayment kaoLaRealRepayment = getKaoLaRealRepayment(summary, bankCardAndOrderId);
        kaoLaRealRepayment.setRepayMethod(NORMAL_REPAY.getType());
        kaoLaRealRepayment.setInterestSplit(kaoLaProjectRepayment.getPlatformManageFee());
        kaoLaRealRepayment.setPrincipal(kaoLaProjectRepayment.getRepayPrincipal());
        kaoLaRealRepayment.setInterest(kaoLaProjectRepayment.getRepayInterest().add(kaoLaProjectRepayment.getPlatformManageFee()));
        kaoLaRealRepayment.setRepayAmount(kaoLaProjectRepayment.getPlatformManageFee().add(kaoLaProjectRepayment.getRepayPrincipal()).add(
                kaoLaProjectRepayment.getRepayInterest()));
        kaoLaRealRepaymentMapper.insertSelective(kaoLaRealRepayment);
        return kaoLaRealRepayment;
    }

    private KaoLaRealRepayment initPreRepayment(KaoLaLiCaiRepayRequestDTO dto,ProjectBorrowerSummary summary) {
        Tuple2<BankCardDTO, String> bankCardAndOrderId = getOrderAndIdCard(dto.getUserId(), dto.getProjectId());
        PrepaymentAmountDTO prepaymentAmount = this.getPrepaymentAmount(summary,false);

        KaoLaRealRepayment kaoLaRealRepayment = getKaoLaRealRepayment(summary, bankCardAndOrderId);
        kaoLaRealRepayment.setRepayMethod(PRE_REPAY.getType());
        kaoLaRealRepayment.setInterestSplit(prepaymentAmount.getPlatformManagerFee());
        kaoLaRealRepayment.setPrincipal(prepaymentAmount.getPrincipal());
        kaoLaRealRepayment.setInterest(prepaymentAmount.getInterest().add(prepaymentAmount.getPlatformManagerFee()));
        kaoLaRealRepayment.setRepayAmount(prepaymentAmount.getPrincipal().add(prepaymentAmount.getInterest()).add(prepaymentAmount.getPlatformManagerFee()));
        kaoLaRealRepaymentMapper.insertSelective(kaoLaRealRepayment);
        return kaoLaRealRepayment;
    }

    private KaoLaRealRepayment initOverdueRepayment(ProjectBorrowerSummary summary, Tuple2<BankCardDTO, String> bankCardAndOrderId, boolean isBadDebt, KaoLaRealRepayment buyBackRepayment) {

        //获取当前期的考拉还款计划
        KaoLaProjectRepayment kaoLaProjectRepayment = Optional.ofNullable(kaoLaProjectRepaymentMapper.selectByProjectIdAndPeriod(summary.getProjectId()
                , summary.getCurrentPeriod())).orElseThrow(() -> new BizException("TRD20005"));
        BigDecimal overdueInterest = this.getOverdueInterest(summary, false);
        KaoLaRealRepayment kaoLaRealRepayment = getKaoLaRealRepayment(summary, bankCardAndOrderId);
        kaoLaRealRepayment.setRepayMethod(OVERDUE_REPAY.getType());
        //逾期还款（包括严重逾期还款）还款总额是本金+利息+罚息，利息分账如果是负数则需要减去（在利息里面减去），正数则加到罚息
        BigDecimal platformManageFee = kaoLaProjectRepayment.getPlatformManageFee();

        kaoLaRealRepayment.setInterestSplit(Optional.ofNullable(platformManageFee).map(BigDecimal::abs).orElse(BigDecimal.ZERO));
        kaoLaRealRepayment.setPrincipal(kaoLaProjectRepayment.getRepayPrincipal());
        if (platformManageFee.compareTo(BigDecimal.ZERO) < 0) {
            kaoLaRealRepayment.setInterest(kaoLaProjectRepayment.getRepayInterest().add(platformManageFee));
            kaoLaRealRepayment.setOverdueInterest(overdueInterest);
        } else {
            kaoLaRealRepayment.setInterest(kaoLaProjectRepayment.getRepayInterest());
            kaoLaRealRepayment.setOverdueInterest(overdueInterest.add(platformManageFee));
        }

        if (isBadDebt) {
            kaoLaRealRepayment.setRepayMethod(BAD_DEBT_REPAY.getType());
            kaoLaRealRepayment.setBadLoansRepayMethod(BadLoansRepayMethodEmun.FRIST_TIME_BAD_DEBT_REPAY.getCode());
        }

        KaoLaRealRepayment advanceRepayment = this.getAdvanceRepayment(summary, summary.getCurrentPeriod());
        if (advanceRepayment != null) {
            //如果是剩余本金大于考拉的本金则将多余的部分放到罚息
            handleAdvance(kaoLaRealRepayment, advanceRepayment);
        }

        kaoLaRealRepayment.setRepayAmount(kaoLaRealRepayment.getPrincipal().add(kaoLaRealRepayment.getInterest()));
        kaoLaRealRepayment.setDamages(BigDecimal.ZERO);
        kaoLaRealRepayment.setPayBankId(bankCardAndOrderId.get_1().getBankAccountNo());
        kaoLaRealRepayment.setWithholdingContract(getWithholdingContract(summary));
        kaoLaRealRepaymentMapper.insertSelective(kaoLaRealRepayment);
        return kaoLaRealRepayment;
    }

    private KaoLaRealRepayment initOverduePartRepayment(ProjectBorrowerSummary summary, Tuple2<BankCardDTO, String> bankCardAndOrderId,  boolean isBadDebt) {

        //获取当前期的考拉还款计划，这里也可以直接取你我还款计划中的数据直接减掉已还部分，就像立即还款的时候获取总还款额哪里也是这样的
        ProjectCreditorFund projectCreditorFund = Optional.ofNullable(projectCreditorFundMapper.selectByProjectIdAndPeriod(summary.getProjectId()
                , summary.getCurrentPeriod())).orElseThrow(() -> new BizException("TRD20005"));
        KaoLaProjectRepayment kaoLaProjectRepayment = Optional.ofNullable(kaoLaProjectRepaymentMapper.selectByProjectIdAndPeriod(summary.getProjectId()
                , summary.getCurrentPeriod())).orElseThrow(() -> new BizException("TRD20005"));
        BigDecimal overdueInterest = this.getOverdueInterest(summary, false);
        BigDecimal platformManageFee = getOverduePartPlatformManageFee(summary);

        KaoLaRealRepayment kaoLaRealRepayment = getKaoLaRealRepayment(summary, bankCardAndOrderId);
        kaoLaRealRepayment.setRepayMethod(OVERDUE_REPAY.getType());
        kaoLaRealRepayment.setInterestSplit(platformManageFee);
        kaoLaRealRepayment.setInterest(projectCreditorFund.getInterest());
        kaoLaRealRepayment.setPrincipal(projectCreditorFund.getCapital());
        kaoLaRealRepayment.setOverdueInterest(overdueInterest.add(platformManageFee));

        //如果是剩余本金大于考拉的本金则将多余的部分放到罚息
        if (projectCreditorFund.getCapital().compareTo(kaoLaProjectRepayment.getRepayPrincipal()) > 0) {
            BigDecimal redundance = projectCreditorFund.getCapital().subtract(kaoLaProjectRepayment.getRepayPrincipal());
            kaoLaRealRepayment.setPrincipal(kaoLaProjectRepayment.getRepayPrincipal());
            kaoLaRealRepayment.setOverdueInterest(kaoLaRealRepayment.getOverdueInterest().add(redundance));

            //将多余的都放到利息分账，方便后面统计罚息
            kaoLaRealRepayment.setInterestSplit(kaoLaRealRepayment.getInterestSplit().add(redundance));
        }

        //算出实际代偿的利息
        if (kaoLaProjectRepayment.getPlatformManageFee().compareTo(BigDecimal.ZERO) < 0) {
            kaoLaProjectRepayment.setRepayInterest(kaoLaProjectRepayment.getRepayInterest().add(kaoLaProjectRepayment.getPlatformManageFee()));
        }
        //利息不能比考拉的大，大于则将多余的部分放到罚息
        if (projectCreditorFund.getInterest().compareTo(kaoLaProjectRepayment.getRepayInterest()) > 0) {
            BigDecimal redundance = projectCreditorFund.getInterest().subtract(kaoLaProjectRepayment.getRepayInterest());
            kaoLaRealRepayment.setInterest(kaoLaProjectRepayment.getRepayInterest());
            kaoLaRealRepayment.setOverdueInterest(kaoLaRealRepayment.getOverdueInterest().add(redundance));
            kaoLaRealRepayment.setInterestSplit(kaoLaRealRepayment.getInterestSplit().add(redundance));
        }

        kaoLaRealRepayment.setRepayAmount(kaoLaRealRepayment.getPrincipal().add(
                kaoLaRealRepayment.getInterest()));
        if (isBadDebt) {
            kaoLaRealRepayment.setRepayMethod(BAD_DEBT_REPAY.getType());
            kaoLaRealRepayment.setBadLoansRepayMethod(BadLoansRepayMethodEmun.FRIST_TIME_BAD_DEBT_REPAY.getCode());
        }
        kaoLaRealRepayment.setDamages(BigDecimal.ZERO);
        kaoLaRealRepayment.setPayBankId(bankCardAndOrderId.get_1().getBankAccountNo());
        kaoLaRealRepayment.setWithholdingContract(getWithholdingContract(summary));
        kaoLaRealRepaymentMapper.insertSelective(kaoLaRealRepayment);
        return kaoLaRealRepayment;
    }

    private KaoLaRealRepayment buyBackRepayment(ProjectBorrowerSummary summary, Tuple2<BankCardDTO, String> bankCardAndOrderId, KaoLaRealRepayment realRepay) {

        //获取当前期的考拉还款计划，这里也可以直接取你我还款计划中的数据直接减掉已还部分，就像立即还款的时候获取总还款额哪里也是这样的
        ProjectCreditorFund projectCreditorFund = Optional.ofNullable(projectCreditorFundMapper.selectByProjectIdAndPeriod(summary.getProjectId()
                , summary.getCurrentPeriod())).orElseThrow(() -> new BizException("TRD20005"));
        BigDecimal overdueInterest = this.getOverdueInterest(summary, false);
        BigDecimal platformManageFee = getOverduePartPlatformManageFee(summary);

        KaoLaRealRepayment kaoLaRealRepayment = getKaoLaRealRepayment(summary, bankCardAndOrderId);
        kaoLaRealRepayment.setRepayMethod(OVERDUE_REPAY.getType());
        kaoLaRealRepayment.setInterestSplit(platformManageFee);
        kaoLaRealRepayment.setInterest(projectCreditorFund.getInterest());
        kaoLaRealRepayment.setPrincipal(projectCreditorFund.getCapital());
        kaoLaRealRepayment.setOverdueInterest(overdueInterest.add(platformManageFee));

        //需先将代偿那一期还掉后才能将剩余的全部还到坏账那一期
        List<KaoLaRealRepayment> advanceSuccRepay = kaoLaRealRepaymentMapper.selectRepayedAdvance(summary.getProjectId(), BadLoansRepayMethodEmun.FRIST_TIME_BAD_DEBT_REPAY.getCode());
        if (CollectionUtils.isEmpty(advanceSuccRepay)) {
            KaoLaRealRepayment advanceRepayment = getAdvanceRepayment(summary, realRepay.getRepayNo() - 1);
            if (Objects.isNull(advanceRepayment)) {
                advanceRepayment = new KaoLaRealRepayment();
                BeanUtils.copyProperties(realRepay, advanceRepayment);
            }

            //如果是剩余本金大于考拉的本金则将多余的部分放到罚息
            if (projectCreditorFund.getCapital().compareTo(advanceRepayment.getPrincipal()) > 0) {
                BigDecimal redundance = projectCreditorFund.getCapital().subtract(advanceRepayment.getPrincipal());
                kaoLaRealRepayment.setPrincipal(advanceRepayment.getPrincipal());
                kaoLaRealRepayment.setOverdueInterest(kaoLaRealRepayment.getOverdueInterest().add(redundance));

                //将多余的都放到利息分账，方便后面统计罚息
                kaoLaRealRepayment.setInterestSplit(kaoLaRealRepayment.getInterestSplit().add(redundance));
            }

            //利息不能比考拉的大，大于则将多余的部分放到罚息
            if (projectCreditorFund.getInterest().compareTo(advanceRepayment.getInterest()) > 0) {
                BigDecimal redundance = projectCreditorFund.getInterest().subtract(advanceRepayment.getInterest());
                kaoLaRealRepayment.setInterest(advanceRepayment.getInterest());
                kaoLaRealRepayment.setOverdueInterest(kaoLaRealRepayment.getOverdueInterest().add(redundance));
                kaoLaRealRepayment.setInterestSplit(kaoLaRealRepayment.getInterestSplit().add(redundance));
            }
            kaoLaRealRepayment.setRepayNo(advanceRepayment.getRepayNo());
            kaoLaRealRepayment.setBadLoansRepayMethod(BadLoansRepayMethodEmun.FRIST_TIME_BAD_DEBT_REPAY.getCode());
        } else {
            List<KaoLaRealRepayment> buyBackRepays = kaoLaRealRepaymentMapper.selectRepayedAdvance(summary.getProjectId(), BadLoansRepayMethodEmun.SECOND_TIME_BAD_DEBT_REPAY.getCode());
            BigDecimal repayedPrincipal = BigDecimal.ONE;
            BigDecimal repayedInterest = BigDecimal.ONE;
            for (KaoLaRealRepayment buyBackRepay : buyBackRepays) {
                repayedPrincipal = repayedPrincipal.add(buyBackRepay.getPrincipal());
                repayedInterest = repayedInterest.add(buyBackRepay.getInterest());
            }
            //如果是剩余本金大于考拉的本金则将多余的部分放到罚息
            if (projectCreditorFund.getCapital().add(repayedPrincipal).compareTo(realRepay.getPrincipal()) > 0) {
                BigDecimal redundance = projectCreditorFund.getCapital().subtract(realRepay.getPrincipal().subtract(repayedPrincipal));
                kaoLaRealRepayment.setPrincipal(realRepay.getPrincipal().subtract(repayedPrincipal));
                kaoLaRealRepayment.setOverdueInterest(kaoLaRealRepayment.getOverdueInterest().add(redundance));

                //将多余的都放到利息分账，方便后面统计罚息
                kaoLaRealRepayment.setInterestSplit(kaoLaRealRepayment.getInterestSplit().add(redundance));
            }

            //利息不能比考拉的大，大于则将多余的部分放到罚息
            if (projectCreditorFund.getInterest().add(repayedInterest).compareTo(realRepay.getInterest()) > 0) {
                BigDecimal redundance = projectCreditorFund.getInterest().subtract(realRepay.getInterest().subtract(repayedInterest));
                kaoLaRealRepayment.setInterest(realRepay.getInterest().subtract(repayedInterest));
                kaoLaRealRepayment.setOverdueInterest(kaoLaRealRepayment.getOverdueInterest().add(redundance));
                kaoLaRealRepayment.setInterestSplit(kaoLaRealRepayment.getInterestSplit().add(redundance));
            }
            kaoLaRealRepayment.setRepayNo(realRepay.getRepayNo());
            kaoLaRealRepayment.setBadLoansRepayMethod(BadLoansRepayMethodEmun.SECOND_TIME_BAD_DEBT_REPAY.getCode());
        }

        kaoLaRealRepayment.setRepayAmount(kaoLaRealRepayment.getPrincipal().add(
                kaoLaRealRepayment.getInterest()));
        kaoLaRealRepayment.setDamages(BigDecimal.ZERO);
        kaoLaRealRepayment.setPayBankId(bankCardAndOrderId.get_1().getBankAccountNo());
        kaoLaRealRepayment.setWithholdingContract(getWithholdingContract(summary));
        kaoLaRealRepaymentMapper.insertSelective(kaoLaRealRepayment);
        return kaoLaRealRepayment;
    }

    /**
     * 坏账还款.
     * <p>
     * 还款总额 = 所有待还本金 + 所有待还平台管理费 + 所有待还担保服务费 + 当前期利息
     *
     * @param summary 标的还款概要
     * @param bankCardAndOrderId 账户信息
     * @return 考拉实际还款
     */
    private KaoLaRealRepayment initBadDebtRepayment(ProjectBorrowerSummary summary, Tuple2<BankCardDTO, String> bankCardAndOrderId) {
        PrepaymentAmountDTO prepaymentAmount = this.getPrepaymentAmount(summary,true);
        BigDecimal overdueInterest = this.getOverdueInterest(summary, true);

        KaoLaRealRepayment kaoLaRealRepayment = getKaoLaRealRepayment(summary, bankCardAndOrderId);
        kaoLaRealRepayment.setRepayMethod(KaoLaRealRepayment.RepayMethodEnum.BAD_DEBT_REPAY.getType());
        if (prepaymentAmount.getPlatformManagerFee().compareTo(BigDecimal.ZERO) < 0) {
            kaoLaRealRepayment.setInterest(prepaymentAmount.getInterest().add(prepaymentAmount.getPlatformManagerFee()));
            kaoLaRealRepayment.setOverdueInterest(overdueInterest);
        } else {
            kaoLaRealRepayment.setInterest(prepaymentAmount.getInterest());
            kaoLaRealRepayment.setOverdueInterest(overdueInterest.add(prepaymentAmount.getPlatformManagerFee()));
        }
        kaoLaRealRepayment.setInterestSplit(prepaymentAmount.getPlatformManagerFee());
        kaoLaRealRepayment.setPrincipal(prepaymentAmount.getPrincipal());

        KaoLaRealRepayment buyBackRepayment = this.getBuyBackRepayment(summary.getProjectId());
        if (Objects.nonNull(buyBackRepayment)) {
            handleAdvance(kaoLaRealRepayment, buyBackRepayment);
        }
        kaoLaRealRepayment.setRepayAmount(kaoLaRealRepayment.getPrincipal().add(
                kaoLaRealRepayment.getInterest()));

        //考拉是根据本金+利息+罚息 去计算总金额的，所以这里把平台管理费加到罚息里面
        kaoLaRealRepayment.setDamages(BigDecimal.ZERO);
        kaoLaRealRepayment.setBadLoansRepayMethod(BadLoansRepayMethodEmun.SECOND_TIME_BAD_DEBT_REPAY.getCode());
        kaoLaRealRepayment.setPayBankId(bankCardAndOrderId.get_1().getBankAccountNo());
        kaoLaRealRepayment.setWithholdingContract(getWithholdingContract(summary));

        //因逾期还款成功后当前期并未推进一期，所以这里要加上一期后传给考拉
        if (!summary.getCurrentPeriod().equals(summary.getTotalPeriod())) {
            kaoLaRealRepayment.setRepayNo(kaoLaRealRepayment.getRepayNo() + 1);
        }
        kaoLaRealRepaymentMapper.insertSelective(kaoLaRealRepayment);
        return kaoLaRealRepayment;
    }

    private void handleAdvance(KaoLaRealRepayment kaoLaRealRepayment, KaoLaRealRepayment buyBackRepayment) {
        //如果是剩余本金大于考拉的本金则将多余的部分放到罚息
        if (kaoLaRealRepayment.getPrincipal().compareTo(buyBackRepayment.getPrincipal()) > 0) {
            BigDecimal redundance = kaoLaRealRepayment.getPrincipal().subtract(buyBackRepayment.getPrincipal());
            kaoLaRealRepayment.setPrincipal(buyBackRepayment.getPrincipal());
            kaoLaRealRepayment.setOverdueInterest(kaoLaRealRepayment.getOverdueInterest().add(redundance));

            //将多余的都放到利息分账，方便后面统计罚息
            kaoLaRealRepayment.setInterestSplit(kaoLaRealRepayment.getInterestSplit().add(redundance));
        }

        //利息不能比考拉的大，大于则将多余的部分放到罚息
        if (kaoLaRealRepayment.getInterest().compareTo(buyBackRepayment.getInterest()) > 0) {
            BigDecimal redundance = kaoLaRealRepayment.getInterest().subtract(buyBackRepayment.getInterest());
            kaoLaRealRepayment.setInterest(buyBackRepayment.getInterest());
            kaoLaRealRepayment.setOverdueInterest(kaoLaRealRepayment.getOverdueInterest().add(redundance));
            kaoLaRealRepayment.setInterestSplit(kaoLaRealRepayment.getInterestSplit().add(redundance));
        }
    }

    /**
     * 坏账部分还款.
     * <p>
     * 还款总额 = 所有待还本金 + 所有待还平台管理费 + 所有待还担保服务费 + 当前期利息
     *
     * @param summary 标的还款概要
     * @param bankCardAndOrderId 账户信息
     * @return 考拉实际还款
     */
    private KaoLaRealRepayment initBadDebtPartRepayment(ProjectBorrowerSummary summary, Tuple2<BankCardDTO, String> bankCardAndOrderId) {
        PrepaymentAmountDTO prepaymentAmount = this.calcNiiWooPreRePay(summary, true);

        PrepaymentAmountDTO nonPartPrepaymentAmount = this.getPrepaymentAmount(summary,true);
        BigDecimal overdueInterest = this.getOverdueInterest(summary, true);

        KaoLaRealRepayment kaoLaRealRepayment = getKaoLaRealRepayment(summary, bankCardAndOrderId);
        kaoLaRealRepayment.setRepayMethod(KaoLaRealRepayment.RepayMethodEnum.BAD_DEBT_REPAY.getType());
        kaoLaRealRepayment.setInterestSplit(prepaymentAmount.getPlatformManagerFee());
        kaoLaRealRepayment.setPrincipal(prepaymentAmount.getPrincipal());
        kaoLaRealRepayment.setInterest(prepaymentAmount.getInterest());
        kaoLaRealRepayment.setOverdueInterest(overdueInterest.add(prepaymentAmount.getPlatformManagerFee()));

        //本金不能比考拉的大，大于则将多余的部分放到罚息
        if (prepaymentAmount.getPrincipal().compareTo(nonPartPrepaymentAmount.getPrincipal()) > 0) {
            BigDecimal redundance = prepaymentAmount.getPrincipal().subtract(nonPartPrepaymentAmount.getPrincipal());
            kaoLaRealRepayment.setPrincipal(nonPartPrepaymentAmount.getPrincipal());
            kaoLaRealRepayment.setOverdueInterest(kaoLaRealRepayment.getOverdueInterest().add(redundance));
            kaoLaRealRepayment.setInterestSplit(kaoLaRealRepayment.getInterestSplit().add(redundance));
        }

        //利息不能比考拉的大，大于则将多余的部分放到罚息
        if (prepaymentAmount.getInterest().compareTo(nonPartPrepaymentAmount.getInterest()) > 0) {
            BigDecimal redundance = prepaymentAmount.getInterest().subtract(nonPartPrepaymentAmount.getInterest());
            kaoLaRealRepayment.setInterest(nonPartPrepaymentAmount.getInterest());
            kaoLaRealRepayment.setOverdueInterest(kaoLaRealRepayment.getOverdueInterest().add(redundance));
            kaoLaRealRepayment.setInterestSplit(kaoLaRealRepayment.getInterestSplit().add(redundance));
        }
        kaoLaRealRepayment.setRepayAmount(kaoLaRealRepayment.getPrincipal().add(
                kaoLaRealRepayment.getInterest()));

        if (!summary.getCurrentPeriod().equals(summary.getTotalPeriod())) {
            kaoLaRealRepayment.setRepayNo(kaoLaRealRepayment.getRepayNo() + 1);
        }

        kaoLaRealRepayment.setDamages(BigDecimal.ZERO);
        kaoLaRealRepayment.setBadLoansRepayMethod(BadLoansRepayMethodEmun.SECOND_TIME_BAD_DEBT_REPAY.getCode());
        kaoLaRealRepayment.setPayBankId(bankCardAndOrderId.get_1().getBankAccountNo());
        kaoLaRealRepayment.setWithholdingContract(getWithholdingContract(summary));
        kaoLaRealRepaymentMapper.insertSelective(kaoLaRealRepayment);
        return kaoLaRealRepayment;
    }

    private KaoLaRealRepayment getKaoLaRealRepayment(ProjectBorrowerSummary summary, Tuple2<BankCardDTO, String> bankCardAndOrderId) {
        KaoLaRealRepayment kaoLaRealRepayment = new KaoLaRealRepayment();
        kaoLaRealRepayment.setId(snowflakeIdWorker.nextId());
        kaoLaRealRepayment.setProjectId(summary.getProjectId());
        kaoLaRealRepayment.setSerialNo(UUID.randomUUID().toString());
        kaoLaRealRepayment.setLoanOrderId(bankCardAndOrderId.get_2());
        kaoLaRealRepayment.setIdCard(bankCardAndOrderId.get_1().getIdCard());
        kaoLaRealRepayment.setRepayNo(summary.getCurrentPeriod());
        kaoLaRealRepayment.setRepayDate(FastDateFormat.getInstance(DatePatternConstant.MM_DD_YYYY).format(new Date()));
        kaoLaRealRepayment.setNiiwooRepayStatus(KaoLaRealRepayment.NiiwooStatusEnum.IN_HAND.getStatus());
        kaoLaRealRepayment.setCreateTime(DateTime.now().toDate());
        return kaoLaRealRepayment;
    }

    private String getWithholdingContract(ProjectBorrowerSummary summary) {
        return projectContractMapperExt.queryByPageWithUserAndType(summary.getBorrowerUserId(),
                FadadaContractTemplateEnum.TemplateTypeEnum.WITHHOLD.getType(), FadadaContractEnum.TradeTypeEnum.LOAN.getType(), 0, 1).stream()
                .findFirst()
                .map(ProjectContract::getDownloadUrl)
                .orElse("No_WithholdingContract");
    }

    public KaoLaLiCaiRepayResponseDTO getResponseForApp(ProjectBorrowerSummary summary, BigDecimal amout, String batchRequestNo ){
        KaoLaLiCaiRepayResponseDTO responseDTO = new KaoLaLiCaiRepayResponseDTO();
        responseDTO.setAppRefundStatus((byte)1);
        responseDTO.setCurrentPeriod(summary.getCurrentPeriod());
        responseDTO.setRepaymentAmount(amout);
        responseDTO.setTitle(summary.getProjectTitle());
        responseDTO.setTotalPeriod(summary.getTotalPeriod());
        responseDTO.setBatchRequestNo(batchRequestNo);
        return responseDTO;
    }

    /**
     * 如果在设定日期和当前日期之间则为0，
     * 如果是在设定日期之前则直接参照设定日期，
     * 如果是在当前日期之后则直接参照当前日期.
     *
     * @param refundDate 还款日期
     * @param isReverse 是否反转，借款首页需要反转，即大于0表示距离还款天数，小于0表示逾期天数
     * @return 前端展示逾期天数
     */
    public Integer getKaolaOverdueDays(Date refundDate , boolean isReverse) {
        Integer overdueDay;
        if (isReverse) {
            overdueDay = RepaymentUtil.getOverdueDay(commonLocalService.getKaolaCurrentDate(), refundDate);
            Integer realOverdueDay = RepaymentUtil.getOverdueDay(new Date(), refundDate);
            if (overdueDay >= 0 && realOverdueDay <= 0) {
                overdueDay = 0;
            } else if (realOverdueDay > 0) {
                overdueDay = realOverdueDay;
            }
        } else {
            overdueDay = RepaymentUtil.getOverdueDay(refundDate, commonLocalService.getKaolaCurrentDate());
            Integer realOverdueDay = RepaymentUtil.getOverdueDay(refundDate, new Date());
            if (overdueDay <= 0 && realOverdueDay >= 0) {
                overdueDay = 0;
            } else if (realOverdueDay < 0) {
                overdueDay = realOverdueDay;
            }
        }
        return overdueDay;
    }

    public Integer getKaolaOverdueDays(Date refundDate) {
        return getKaolaOverdueDays(refundDate, false);
    }

    /**
     * 定时更新后获取罚息，非实时，存在误差.
     *
     * @param summary 项目概要
     * @return 逾期或坏账罚息
     */
    private BigDecimal getOverdueInterest(ProjectBorrowerSummary summary){
        List<ProjectBorrowerPlan> projectBorrowerPlen = projectBorrowerPlanMapper.selectBorrowerPlanByProjectIdNotRefund(summary.getBorrowerUserId(),
                summary.getProjectId());
        return projectBorrowerPlen.stream().map(ProjectBorrowerPlan::getPenaltyFee).reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /**
     * 获取逾期部分还款平台管理费.
     *
     * @param summary 标的还款概要
     * @return 平台管理费
     */
    private BigDecimal getOverduePartPlatformManageFee(ProjectBorrowerSummary summary) {
        ProjectBorrowerPlan projectBorrowerPlan = Optional.ofNullable(projectBorrowerPlanMapper.selectBorrowerPlanByProjectIdAndCurrentPeriod(summary
                .getProjectId(), summary.getCurrentPeriod())).orElseThrow(() -> new BizException("TRD20005"));
        return projectBorrowerPlan.getGuaranteeFee().subtract(projectBorrowerPlan.getRefundedGuaranteeFee())
                .add(projectBorrowerPlan.getManagementFee().subtract(projectBorrowerPlan.getRefundedManagementFee()));
    }

    private BigDecimal getPenaltyFee(ProjectBorrowerSummary summary){
        BigDecimal penaltyFee = BigDecimal.ZERO;
        List<ProjectBorrowerPlanNew> planList = projectBorrowerPlanNewMapper.selectNeedRefundList(summary.getProjectId(), null);
        for (ProjectBorrowerPlanNew plan : planList) {
            penaltyFee = penaltyFee.add(
                    Optional.ofNullable(borrowerPlanDetailMapperExt.queryProjectBorrowerPlanDetailByPlanIdAndFeeType(plan.getId(), FeeTypeEnum.PENALTY.getValue()))
                            .map(r -> r.getMoney().subtract(r.getRefundMoney()))
                            .orElse(BigDecimal.ZERO)
            );
        }
        return penaltyFee;
    }

    /**
     * 实时计算罚息.
     *
     * @param summary 还款概要
     * @param isBadDebt 是否坏账
     * @return 逾期或坏账罚息
     */
    private BigDecimal getOverdueInterest(ProjectBorrowerSummary summary, boolean isBadDebt){
        List<ProjectBorrowerPlan> plans = projectBorrowerPlanMapper.calcToRepayAmountTotal(summary.getBorrowerUserId(), summary.getProjectId());

        //日切日期，不能直接用当前日期
        Date updateToday = new Date();

        /*1.逾期罚息计算截止日,即还账期的还款日，与日切日期取较小值。注意只是逾期的截止日
          2.如果是只有一期或者最末一期，则逾期截止日就是当前日期
         */
        Date penaltyEndDate = plans.stream()
                .filter(plan -> plan.getCurrentPeriod().equals(summary.getCurrentPeriod() + 1))
                .findFirst()
                .map(plan -> new Date(Math.min(plan.getRefundDate().getTime(), updateToday.getTime())))
                .orElse(updateToday);

        //下面的逻辑需与计划任务的完全保持一致
        BigDecimal penalty;
        if (isBadDebt) {
            if (summary.getCurrentPeriod().equals(summary.getTotalPeriod())) {

                //只剩一期的情况只需走逾期的罚息，因为这个本息还坏账一致，且penaltyEndDate为默认的当前日期，所以不用算坏账期的罚息
                penalty = fundRepaymentService.getTotalPenalty(summary, summary.getCurrentPeriod(), penaltyEndDate, false);
            } else if (isBadDebtRefundOverdue(summary)) {

                //如果是拆分第二次结清则只需计算坏账期的罚息
                penalty = fundRepaymentService.getTotalPenalty(summary, summary.getCurrentPeriod() + 1, updateToday, true);
            } else {
                penalty = fundRepaymentService.getTotalPenalty(summary, summary.getCurrentPeriod(), penaltyEndDate, false)
                        .add(fundRepaymentService.getTotalPenalty(summary, summary.getCurrentPeriod() + 1, updateToday, true));
            }
        } else {
            penalty = fundRepaymentService.getTotalPenalty(summary, summary.getCurrentPeriod(), penaltyEndDate, false);
        }
        return penalty;
    }

    /**
     * 考拉理财计算借款人提前还款应还金额.
     * <p>
     * ①倒数第二期已正常还款，当前期是最后一期则不能提前还款
     * ②如果有3期，第2期逾期，则可以提前结清；如果第2期正常还款，则不能；如果第2期坏账，也不能
     *
     * @param summary 你我金融还款概要
     * @param isBadDebt 是否坏账即严重逾期
     * @return principal:本金 interest:利息 penalty:罚息 cutCosts:节省金额
     */
    public PrepaymentAmountDTO getPrepaymentAmount(ProjectBorrowerSummary summary, boolean isBadDebt) {
        PrepaymentAmountDTO prepaymentAmount = new PrepaymentAmountDTO();
        Long projectId = summary.getProjectId();
        Integer currentPeriod;
        if (isBadDebt) {
            currentPeriod = summary.getCurrentPeriod() + 1;
        } else {
            currentPeriod = summary.getCurrentPeriod();
        }
        List<KaoLaProjectRepayment> kaoLaProjectRepaymentList = kaoLaProjectRepaymentMapper.selectByProjectId(projectId);
        for (KaoLaProjectRepayment kaoLaProjectRepayment : kaoLaProjectRepaymentList) {
            ProjectBorrowerPlan projectBorrowerPlan = projectBorrowerPlanMapper.selectBorrowerPlanByProjectIdAndCurrentPeriod(kaoLaProjectRepayment
                    .getProjectId(), kaoLaProjectRepayment.getRepayNo());

            /*
               需要未还款的所有本金+当前期的利息
             */
            if (projectBorrowerPlan.getRefundStatus().equals(BorrowerPlanRefundStatusEnum.NON_REPAYMENT.getCode())) {

                //未拆分前 坏账则需要当前期和下一期的利息，提前结清则取当前期的利息
                //拆分后，坏账只去当前期一下期的利息即坏账那一期的利息
                if (kaoLaProjectRepayment.getRepayNo().equals(currentPeriod)) {
                    prepaymentAmount.addInterest(kaoLaProjectRepayment.getRepayInterest());
                }
                prepaymentAmount.addPrincipal(kaoLaProjectRepayment.getRepayPrincipal());
            }
        }

        prepaymentAmount.setPlatformManagerFee(getRepayBeforeKaoLaPlatformManagerFee(summary, isBadDebt));

        //因上面计算平台管理费的时候未加上当前期的考拉平台管理费，所以这里加上
        KaoLaProjectRepayment kaoLaProjectRepayment = kaoLaProjectRepaymentMapper.selectByProjectIdAndPeriod(projectId, currentPeriod);
        prepaymentAmount.setPlatformManagerFee(prepaymentAmount.getPlatformManagerFee().add(kaoLaProjectRepayment.getPlatformManageFee()));

        return prepaymentAmount;
    }

    /**
     * 提前结清，请求考拉时的平台管理费.
     * <p>
     * 发送给考拉的请求，一定得以考拉的利息本金，利息为准（因为这个是考拉那边投资人的利益，必须按照考拉还款计划来，不能改变，能改变的只有平台管理费）,
     * 而提前结清除了当前期的本金利息要完全按照考拉的为准外，当前期之后（所以下面的计算的时候会在当前期的基础上+1）的所有本金的差额（因为不要算利息了，所以只要算本金的差额）
     * 需要从平台管理费中增加（考拉利率 < 你我利率时，）或减少（考拉利率 > 你我利率）
     * 每期平台管理费 = 考拉平台管理费 - （（考拉本金 + 考拉平台管理费） - （你我本金+你我平台管理费）)  = 你我本金 + 你我管理费 - 考拉本金
     * 总平台管理费=从当前期下一期开始算的每期平台管理费（上面的公私） + 考拉第一期平台管理费
     *
     * @param summary 项目概要表
     * @return 平台管理费
     */
    private BigDecimal getRepayBeforeKaoLaPlatformManagerFee(ProjectBorrowerSummary summary, boolean isBadDebt) {
        BigDecimal platformManagerFeeAmount = BigDecimal.ZERO;
        Integer currentPeriod;
        if (isBadDebt) {

            //坏账拆分后逾期那一期不需要计算在内
            currentPeriod = summary.getCurrentPeriod() + 2;
        } else {
            currentPeriod = summary.getCurrentPeriod() + 1;
        }

        //获取非利息期之后的所有平台管理费,所以在当前期的基础上+1
        List<ProjectBorrowerPlan> repaymentList = projectBorrowerPlanMapper.selectByProjectIdAndPeriodsList(summary.getProjectId(),currentPeriod);
        for(ProjectBorrowerPlan projectrePayment: repaymentList){
            BigDecimal niiwooRepayAmount = projectrePayment.getCapital().add(projectrePayment.getGuaranteeFee()).add(projectrePayment.getManagementFee());

            //获取考拉相应期数的还款计划，如果不存在则跳过
            KaoLaProjectRepayment kaoLaProjectRepayment = kaoLaProjectRepaymentMapper.selectByProjectIdAndPeriod(projectrePayment.getProjectId(),projectrePayment.getCurrentPeriod());
            if(null == kaoLaProjectRepayment){
                continue;
            }

            BigDecimal platformManagerFee = niiwooRepayAmount.subtract(kaoLaProjectRepayment.getRepayPrincipal());
            platformManagerFeeAmount = platformManagerFeeAmount.add(platformManagerFee);
        }
        return platformManagerFeeAmount;
    }

    private void handleResponse(KaoLaRealRepayment kaoLaRealRepayment, BaseResponse repaymentResponse) {
        this.updateKaoLaRepayRequestResult(kaoLaRealRepayment.getId(), repaymentResponse.getCode(), repaymentResponse.getMsg());
        if (!repaymentResponse.isSuccess()) {
            log.error("考拉理财,项目ID={},请求失败,原因如下：{}", kaoLaRealRepayment.getProjectId(), StringUtils.join(Arrays.asList(repaymentResponse.getCode(),
                    repaymentResponse.getMsg()),"--"));
            throw new BizException("TRD20006");
        }
    }

    private void updateKaoLaRepayRequestResult(Long realRePaymentId,String repayCode,String repayCodeMsg){
        KaoLaRealRepayment kaoLaRealrePayment=new KaoLaRealRepayment();
        kaoLaRealrePayment.setId(realRePaymentId);
        kaoLaRealrePayment.setRepayResultCode(Integer.valueOf(repayCode));
        kaoLaRealrePayment.setRepayResultTips(repayCodeMsg);
        if(!repayCode.equals(BaseResponse.SUCCESS_CODE)){
            kaoLaRealrePayment.setNiiwooRepayStatus(KaoLaRealRepayment.NiiwooStatusEnum.REPAY_FAILED.getStatus());
        }
        kaoLaRealRepaymentMapper.updateByPrimaryKeySelective(kaoLaRealrePayment);
    }

}
